package server;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import server.business.AuctionManager;
import server.domain.User;

/**
 * Uses a thread-pool to accept incoming requests.
 *
 * @author Lukas
 *
 */
public class NetworkService implements Runnable {
	final ServerSocket serverSocket;
	private final DatagramSocket datagramSocket;
	private final ExecutorService pool;
	/**
	 * Manages the running auctions.
	 */
	public final AuctionManager auctionManager;
	
	private HashMap<String, User> users;
	
	LinkedList<Handler> handlerList;
	
	/**
	 * Constructor
	 * @param port The port for the service.
	 * @param poolSize
	 * @throws IOException
	 */
	public NetworkService(int port, int poolSize) throws IOException {
		serverSocket = new ServerSocket(port);
		datagramSocket = new DatagramSocket(port);
		pool = Executors.newFixedThreadPool(poolSize);
		
		users = new HashMap<String, User>();
		auctionManager = new AuctionManager(this);
		handlerList = new LinkedList<Handler>();
	}

	@Override
	public void run() {
		try {
			for (;;) {
				Handler h = new Handler(serverSocket.accept(), this);
				handlerList.add(h);
				pool.execute(h);
			}
		} catch (IOException ex) {
		       System.err.println("Shutdown server");
		} finally {
			try {
				serverSocket.close();
			} catch (IOException e) {
				System.err.println("Socket could not be closed");
			}
			datagramSocket.close();
			pool.shutdown();
			synchronized (handlerList) {
				Iterator<Handler> iter = handlerList.iterator();
				while (iter.hasNext()) {
					iter.next().stop();
				}
			}
			auctionManager.end();
		}
	}
	
	/**
	 * Sends a message to a user; if the user is not online, the message will be saved in a queue
	 * for later delivery. If the user does not exist, nothing happens.
	 * @param user The name of the receiver.
	 * @param message The message.
	 */
	public synchronized void sendMessage(String user, String message) {
		User u = users.get(user);
		if (u == null) {
			return;
		}
		u.getMessageQueue().add(message);
		deliverMessages(user);
	}
	
	/**
	 * Tries to send all messages in the queue; if the user is not online, nothing happens.
	 * @param user The receiver of the messages.
	 */
	public synchronized void deliverMessages(String user) {
		User u = users.get(user);
		if (u.isOnline()) {
			DatagramPacket packet;
			for (int i = 0; i < u.getMessageQueue().size(); i++) {
				String message = u.getMessageQueue().poll();
				byte[] bytes = message.getBytes();
				byte[] fixLengthBytes = new byte[256];
				int max = Math.min(bytes.length, 256);
				for (int j = 0; j < max; j++) {
					fixLengthBytes[j] = bytes[j];
				}
				/*for (int j = bytes.length; j < 256; j++) {
					fixLengthBytes[j] = 0;
				}*/
				packet = new DatagramPacket(fixLengthBytes, 256, u.getSocket().getInetAddress(), u.getUdpPort());
				try {
					datagramSocket.send(packet);
				} catch (IOException e) {
					System.err.println("Message " + message + " could not be sent to " + user);
				}
			}
		}
	}
	
	/**
	 * Manages the login.
	 * @param user The user name.
	 * @param socket The socket over which the user can be reached.
	 * @param udpPort The udp-port over which the user can be reached.
	 * @return <tt>true</tt>, if the login was successful, otherwise <tt>false</tt>.
	 */
	public synchronized boolean login(String user, Socket socket, int udpPort) {
		User u = users.get(user);
		if (u != null) {
			if (u.isOnline()) {
				return false;
			}
		} else {
			u = new User();
			users.put(user, u);
		}
		u.setOnline(true);
		u.setSocket(socket);
		u.setUdpPort(udpPort);
		deliverMessages(user);
		return true;
	}
	
	/**
	 * Manages the logout.
	 * @param user The user name.
	 * @return <tt>true</tt>, if the logout was successful, otherwise <tt>false</tt>.
	 */
	public synchronized boolean logout(String user) {
		User u = users.get(user);
		if (u == null) {
			return false;
		}
		u.setOnline(false);
		return true;
	}
}
